import path from "path";
import { DistStruct } from "../../common/src/CreateDist";
import { RunComponent } from "../../common/src/RunComponent";
import fs from "fs";
import * as cheerio from "cheerio";
import { ConfigItem, CssConfigItem, CssDescItem, DescCompoundType, } from "../../../component";
import { cssAnalyser } from "./utils/cssAnalyser";
import { cssTranslator } from "./utils/cssTranslator";

export class ImplementComponent {
  private runtime: RunComponent;
  private js: string[];
  private css: string[];
  private dom: cheerio.Cheerio;
  private dist: DistStruct;
  private $: cheerio.Root;
  private componentMark: string;
  private indexFilePath: string;

  constructor(runtime: RunComponent, distStructure: DistStruct) {
    this.runtime = runtime;
    this.dist = distStructure;
    this.componentMark = runtime.getModuleId();
    this.js = runtime.getJs();
    this.css = runtime.getCss();
    this.dom = runtime.getDom();

    this.indexFilePath = path.join(this.dist.getRootPath(), "index.html");
    const indexStr = fs.readFileSync(this.indexFilePath).toString();
    this.$ = cheerio.load(indexStr);
    this.preLoad();
    this.appendHTML();
    this.appendResources();
    this.appendCss();
    this.appendJs();
    this.loaded();
  }
  /**
   * handle css template,
   * read comment and convert to option for user to choose
   * convert css mustache syntax to normal css
   */
  private cssPreprocess(css: string) {
    // console.log(css, "css");
    const options: CssConfigItem<
      cheerio.Root,
      JQuery<HTMLElement>
    > = {
      trigger: () => {
        return `#${this.getModuleId()}`;
      },
      domSelector: "DOMSELECTOR",
      desc: css
        .split("\n")
        .filter((val) => {
          return /\/\*[^\*]*\*\//.test(val);
        })
        .map((val) => {
          const anal = new cssAnalyser(val);

          let config: CssDescItem = {
            name: cssTranslator[anal.getKey()],
            value: anal.getVal(),
            inputType: "text",
            cssTagSelector: this.getStyleTagMark(),
            onchange: (newVal, seq, cssContent) => {
              // console.log(newVal, seq, cssContent);
              const finder = (
                content: string,
                pattern: RegExp,
                callback: (val: string, inx: number) => void
              ) => {
                let index = 0,
                  start = 0;
                let arr = [];
                while (true) {
                  const res = content.match(pattern);
                  if (res === null) {
                    if (content.length) {
                      arr.push(content);
                    }
                    break;
                  }
                  start = res.index as number;
                  start && arr.push(content.substring(0, start));
                  arr.push(callback(res[0], index++));
                  content = content.substring(start + res[0].length);
                }

                return arr;
              };
              // last = css.indexOf("/*", last) + 2;
              return finder(
                cssContent,
                /([a-zA-Z].*?\*\/)|(\/\*.*?\n)/,
                (val, inx) => {
                  // console.log(val, inx, seq);

                  if (inx !== +seq) return val;

                  return val.replace(/([a-zA-Z\-]*?):.*?;/, `$1: ${newVal};`);
                }
              ).join("");
            },
          };

          try {
            const com = anal
              .getComment()
              .substring(2, anal.getComment().length - 2);
            if (com.length === 0) return config;
            const alternative = JSON.parse(com);

            for (let key in alternative) {
              config[key as unknown as keyof DescCompoundType<JQueryStatic, JQuery<HTMLElement>>] =
                alternative[key];
            }
          } catch (e) {
            console.log(
              "parse css config error!",
              anal.getComment().substring(2, anal.getComment().length - 2)
            );
          }
          return config;
        }),
    };

    // console.log(options, "opt");

    return {
      css,
      options,
    };
  }

  /**
   * scope js with module name exposed!
   * @param js string of all js file
   * @returns scoped js file
   */
  private jsPreprocess(js: string) {
    return `((moduleName)=>{ \n${js}\n})('${this.runtime.getModuleId()}')`;
  }

  private preLoad() {
    const mod = this.runtime.getModule();
    mod.beforeLoad &&
      mod.beforeLoad(
        this.$,
        this.$("#" + this.runtime.getModuleId()),
        this.runtime.getResourceList(),
        this.runtime.getModuleId()
      );
  }
  private loaded() {
    const mod = this.runtime.getModule();
    mod.afterLoad &&
      mod.afterLoad(
        this.$,
        this.$("#" + this.runtime.getModuleId()),
        this.runtime.getResourceList(),
        this.runtime.getModuleId()
      );
  }

  /**
   * append component dom to html
   */
  private appendHTML() {
    const frag = this.dom.clone();
    this.$("body").append(frag);
  }
  private appendCss() {
    const cssStr = this.css.join("");
    const cssProfile = this.cssPreprocess(cssStr);
    this.appendElement("style", cssProfile.css);
    //${cssProfile.options.trigger(this.$)}
    this.appendElement(
      "script",
      `
      if(!window.lowCode.configCssArr)window.lowCode.configCssArr = [];
      window.lowCode.configCssArr.push({
         trigger: ()=>{ return "#${this.getModuleId()}" },
         desc: ${JSON.stringify(cssProfile.options.desc, (k, v) => {
        if (typeof v !== "function") return v;
        return v.toString();
      })} 
      });
     `
    );
    // console.log(this.$.html(), "html...");
  }
  private appendJs() {
    const jsStr = this.js.join("\n");

    this.appendElement("script", this.jsPreprocess(jsStr));
  }

  private appendResources() {
    const resList = JSON.stringify(this.runtime.getResourceList());

    const moduleInfo = this.runtime.getModuleId().split("_");
    this.appendElement(
      "script",
      `
    if(!window.lowCode)window.lowCode = {};
    if(!window.lowCode.resources)window.lowCode.resources = [];
    window.lowCode.resources.push(...${resList});
    if(!window.lowCode.components)window.lowCode.components = [];
    window.lowCode.components.push({"componentId":"${moduleInfo[1]}", "componentName":"${moduleInfo[0]}"})
    `
    );
  }
  private appendElement(tag: "style" | "script" | "html", content: string) {
    let tagMark = "";

    switch (tag) {
      case "style":
        tagMark = this.getStyleTagMark();
        break;
      case "script":
        tagMark = this.getScriptTagMark();
        break;
    }
    const exist = this.$("#" + tagMark);
    // if (exist.length) exist.remove();
    switch (tag) {
      case "style":
        if (exist.length) {
          exist.text(exist.text() + "\n" + content);
        } else {
          this.$("head").append(`<style id="${tagMark}">${content}</style>`);
        }
        break;
      case "script":
        if (exist.length) {
          exist.text(exist.text() + "\n" + content);
        } else {
          this.$("body").append(`<script id="${tagMark}">${content}</script>`);
        }
        break;
      case "html":
        this.$("body").append(content);
        break;
    }
  }

  public write() {
    const html = this.$.html();
    // console.log(html, "html..");

    if (!html) throw Error("cannot find html element!");
    fs.writeFileSync(this.indexFilePath, html);
  }

  public getScriptTagMark() {
    return `script_${this.componentMark}`;
  }

  public getStyleTagMark() {
    return `style_${this.componentMark}`;
  }

  public getModuleId() {
    return this.componentMark;
  }
}
